﻿//-----------------------------------------------------------------------
// <copyright file="SQLServerConnectedRepositoryBase.cs" company="OOHM PROCESSAMENTO DE DADOS LTDA.">
//     Microsoft Public License (MS-PL) This license governs use of the accompanying
//     software. If you use the software, you accept this license. If you do not
//     accept the license, do not use the software. 1. Definitions The terms "reproduce,"
//     "reproduction," "derivative works," and "distribution" have the same meaning
//     here as under U.S. copyright law. A "contribution" is the original software,
//     or any additions or changes to the software. A "contributor" is any person
//     that distributes its contribution under this license. "Licensed patents"
//     are a contributor's patent claims that read directly on its contribution.
//     2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license,
//     including the license conditions and limitations in section 3, each contributor
//     grants you a non-exclusive, worldwide, royalty-free copyright license to
//     reproduce its contribution, prepare derivative works of its contribution,
//     and distribute its contribution or any derivative works that you create.
//     (B) Patent Grant- Subject to the terms of this license, including the license
//     conditions and limitations in section 3, each contributor grants you a non-exclusive,
//     worldwide, royalty-free license under its licensed patents to make, have
//     made, use, sell, offer for sale, import, and/or otherwise dispose of its
//     contribution in the software or derivative works of the contribution in
//     the software. 3. Conditions and Limitations (A) No Trademark License- This
//     license does not grant you rights to use any contributors' name, logo, or
//     trademarks. (B) If you bring a patent claim against any contributor over
//     patents that you claim are infringed by the software, your patent license
//     from such contributor to the software ends automatically. (C) If you distribute
//     any portion of the software, you must retain all copyright, patent, trademark,
//     and attribution notices that are present in the software. (D) If you distribute
//     any portion of the software in source code form, you may do so only under
//     this license by including a complete copy of this license with your distribution.
//     If you distribute any portion of the software in compiled or object code
//     form, you may only do so under a license that complies with this license.
//     (E) The software is licensed "as-is." You bear the risk of using it. The
//     contributors give no express warranties, guarantees or conditions. You may
//     have additional consumer rights under your local laws which this license
//     cannot change. To the extent permitted under your local laws, the contributors
//     exclude the implied warranties of merchantability, fitness for a particular
// </copyright>
//-----------------------------------------------------------------------
namespace AbstractSaaSTemplate.Infrastructure.Win2K8WithSQLServer{    using System;
    using System.Data.EntityClient;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using AbstractSaaSTemplate.Domain;
    using AbstractSaaSTemplate.Domain.Models.Attributes;
    using AbstractSaaSTemplate.Infrastructure.Win2K8WithSQLServer.Repositories.Core;

    /// <summary>
    /// Defines basic functionality and contract for any repository
    /// implemented by this module.
    /// </summary>
    public abstract class SQLServerConnectedRepositoryBase
    {
        /// <summary>
        /// Initializes a new instance of the SQLServerConnectedRepositoryBase class.
        /// </summary>
        /// <param name="loaderForConnectionString">The delegate capable of retrieving the connection string for the EF4 context.</param>
        protected SQLServerConnectedRepositoryBase(Func<string> loaderForConnectionString)
        {
            this.LoaderForConnectionString = loaderForConnectionString;
        }
        
        /// <summary>
        /// Gets the Connection String.
        /// </summary>
        internal Func<string> LoaderForConnectionString { get; private set; }

        /// <summary>
        /// Copies the content of all properties with matching names
        /// accross the Domain Entity and the EF4 Entity.
        /// </summary>
        /// <typeparam name="TEF4Entity">The type of the EF4 Domain Entity.</typeparam>
        /// <typeparam name="TDomainEntity">The type of the Domain Entity.</typeparam>
        /// <param name="databaseEntity">The EF4 entity.</param>
        /// <param name="domainEntity">The domain entity.</param>
        internal static void CopyFromEF4ToDomain<TEF4Entity, TDomainEntity>(
            TEF4Entity databaseEntity,
            TDomainEntity domainEntity) where TDomainEntity : PersistentEntityBase
        {
            CopyAcrossDomainAndLinq<TEF4Entity, TDomainEntity>(
                databaseEntity,
                domainEntity,
                ReplicationDirection.EF4ToDomain);
        }

        /// <summary>
        /// Copies the content of all properties with matching names
        /// accross the Domain Entity and the EF4 Entity.
        /// </summary>
        /// <typeparam name="TDomainEntity">The type of the domain entity</typeparam>
        /// <typeparam name="TEF4Entity">The type of the </typeparam>
        /// <param name="domainEntity">The domain entity</param>
        /// <param name="databaseEntity">The EF4 entity</param>
        internal static void CopyFromDomainToEF4<TDomainEntity, TEF4Entity>(
            TDomainEntity domainEntity,
            TEF4Entity databaseEntity) where TDomainEntity : PersistentEntityBase
        {
            CopyAcrossDomainAndLinq<TEF4Entity, TDomainEntity>(
                databaseEntity,
                domainEntity,
                ReplicationDirection.DomainToEF4);
        }

        /// <summary>
        /// Retrieves a new disposable instance of context
        /// </summary>
        /// <returns>Retrieves a new context which should be used with the using () {} syntax.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Reliability",
            "CA2000:Dispose objects before losing scope",
            Justification = "This is passed below to the inherited classes to control. Unfortunately, we have to truste they will use the using clause properly.")]
        internal ConnectorForSQL2K8 NewContext()
        {
            var context = null as ConnectorForSQL2K8;
            var connectionString = null as string;
            if (this.LoaderForConnectionString != null)
            {
                connectionString = this.LoaderForConnectionString();
            }

            if (!string.IsNullOrWhiteSpace(connectionString))
            {
                var entityConnection = new EntityConnection(connectionString);
                context = new ConnectorForSQL2K8(entityConnection);
            }
            else
            {
                context = new ConnectorForSQL2K8();
            }

            return context;
        }

        /// <summary>
        /// Copy data accross entity from EF4 and domain, in the direction defined,
        /// only changing the value if different from source, to avoid unnecessary
        /// change report.
        /// </summary>
        /// <typeparam name="TEF4Entity">The type of the EF4 Model Entity.</typeparam>
        /// <typeparam name="TDomainEntity">The type of Domain Model Entity.</typeparam>
        /// <param name="databaseEntity">The entity of the databse.</param>
        /// <param name="domainEntity">The entity of the domain.</param>
        /// <param name="direction">The direction of the copy process.</param>
        private static void CopyAcrossDomainAndLinq<TEF4Entity, TDomainEntity>(
            TEF4Entity databaseEntity,
            TDomainEntity domainEntity,
            ReplicationDirection direction) where TDomainEntity : PersistentEntityBase
        {
            // we ensure required arguments
            if (databaseEntity == null)
            {
                throw new ArgumentNullException("databaseEntity");
            }

            if (domainEntity == null)
            {
                throw new ArgumentNullException("domainEntity");
            }

            if (direction == ReplicationDirection.NotSet)
            {
                throw new ArgumentNullException("direction");
            }

            // then we run reflection to find all
            // all properties available in the source
            var properties = null as PropertyInfo[];
            var source = null as object;
            var destination = null as object;

            if (direction == ReplicationDirection.DomainToEF4)
            {
                properties = typeof(TDomainEntity).GetProperties();
                source = domainEntity;
                destination = databaseEntity;
            }
            else
            {
                properties = typeof(TEF4Entity).GetProperties();
                source = databaseEntity;
                destination = domainEntity;
            }

            // load all destination properties
            var destinationProperties = destination.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);

            // finally, we copy all data from the source into the destination
            foreach (var property in properties)
            {
                var readOnly = property.GetCustomAttributes(
                    typeof(ReadOnlyAttribute),
                    true).Length != 0;

                if (readOnly)
                {
                    continue;
                }

                var sourceValue = property.GetValue(source, null);
                var destinationProperty = destinationProperties
                    .Where(d => d.Name == property.Name)
                    .FirstOrDefault();
                
                dynamic destinationValue = null;
                if (destinationProperty != null && destinationProperty is PropertyInfo)
                {
                    destinationValue = destinationProperty.GetValue(destination, null);
                }

                // if it is from domain to EF4 and the property is
                // CreatedAt, we should not change or update such feature.
                if (direction == ReplicationDirection.DomainToEF4 &&
                    destinationProperty.Name == "CreatedAt")
                {
                    continue;
                }

                // if the destination value is not null or
                // it is equal to the sourceValue, that is
                // we don't really need to copy it. this way
                // we avoid to "ReportChange" unnecessarely.
                if (destinationValue != null)
                {
                    if (destinationValue.Equals(sourceValue))
                    {
                        continue;
                    }
                }

                if (destinationProperty != null)
                {
                    // protects from attempts to convert null values into value types
                    if (destinationProperty.PropertyType.IsValueType && sourceValue == null)
                    {
                        continue;
                    }
                    
                    // if the property source and destination are different
                    // we shall not attempt to convert to avoid errors.
                    if (property.PropertyType != destinationProperty.PropertyType)
                    {
                        continue;
                    }

                    // sets the destination property with the source value.
                    // we may check for Nullable types, the can't be converted
                    // from the native data type
                    if (destinationProperty.PropertyType.IsGenericType &&
                        destinationProperty.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                    {
                        destinationProperty.SetValue(
                            destination,
                            sourceValue,
                            null);
                    }
                    else
                    {
                        destinationProperty.SetValue(
                            destination,
                            Convert.ChangeType(sourceValue, destinationProperty.PropertyType, CultureInfo.InvariantCulture),
                            null);
                    }
                }
            }
        }
    }
}


